package main

import (
	"fmt"
	"sync"
)

type fakeResult struct {
	body string
	urls []string
}

func Serial(url string, Fetcher fakeFetcher, fetched map[string]bool) {
	// 判断是否爬过
	if fetched[url] {
		return
	}
	// 开始爬
	urls, _ := Fetcher.Fetch(url)
	fetched[url] = true
	// 获取结果，新的url重新爬取
	for _, u := range urls {
		Serial(u, Fetcher, fetched)
	}
}

var mu sync.Mutex

// 直接构建锁的词典对象比较好
func ConcurrentMutex(url string, Fetcher fakeFetcher, fetched map[string]bool) {
	mu.Lock()
	if fetched[url] {

		mu.Unlock()
		return
	}

	urls, _ := Fetcher.Fetch(url)

	fetched[url] = true
	mu.Unlock()
	var wg sync.WaitGroup

	for _, u := range urls {
		wg.Add(1)
		go func(u string) {
			ConcurrentMutex(u, Fetcher, fetched)
			wg.Done()
		}(u)
	}

	wg.Wait()
}

func worker(url string, Fetcher fakeFetcher, ch chan []string) {
	urls, _ := Fetcher.Fetch(url)
	ch <- urls
}

func coordinator(Fetcher fakeFetcher, ch chan []string) {
	// 接受channel
	// 判断是否执行
	// 判断推出机制,worker 为0
	n := 1
	fetcher := make(map[string]bool)
	for urls := range ch {
		for _, url := range urls {
			if !fetcher[url] {
				n = n + 1
				go worker(url, Fetcher, ch)
				fetcher[url] = true
			}
		}
		n = n - 1
		if n == 0 {
			break
		}

	}
}

func ConcurrentChannel(url string, Fetcher fakeFetcher) {
	ch := make(chan []string)
	// 合并流程
	// 直接把url插入调度中
	// 而不是启动worker
	go func() {
		ch <- []string{url}
	}()

	coordinator(Fetcher, ch)
}

type fakeFetcher map[string]*fakeResult

func main() {
	fmt.Printf("=== Serial===\n")
	Serial("http://golang.org/", fetcher, make(map[string]bool))

	fmt.Printf("=== ConcurrentMutex ===\n")
	ConcurrentMutex("http://golang.org/", fetcher, make(map[string]bool))

	fmt.Printf("=== ConcurrentChannel ===\n")
	ConcurrentChannel("http://golang.org/", fetcher)
}

func (f fakeFetcher) Fetch(url string) ([]string, error) {
	if res, ok := f[url]; ok {
		fmt.Printf("found:   %s\n", url)
		return res.urls, nil
	}
	fmt.Printf("missing: %s\n", url)
	return nil, fmt.Errorf("not found: %s", url)
}

// fetcher is a populated fakeFetcher.
var fetcher = fakeFetcher{
	"http://golang.org/": &fakeResult{
		"The Go Programming Language",
		[]string{
			"http://golang.org/pkg/",
			"http://golang.org/cmd/",
		},
	},
	"http://golang.org/pkg/": &fakeResult{
		"Packages",
		[]string{
			"http://golang.org/",
			"http://golang.org/cmd/",
			"http://golang.org/pkg/fmt/",
			"http://golang.org/pkg/os/",
		},
	},
	"http://golang.org/pkg/fmt/": &fakeResult{
		"Package fmt",
		[]string{
			"http://golang.org/",
			"http://golang.org/pkg/",
		},
	},
	"http://golang.org/pkg/os/": &fakeResult{
		"Package os",
		[]string{
			"http://golang.org/",
			"http://golang.org/pkg/",
		},
	},
}
